/*
 * Copyright (c) 2012-2020 MIRACL UK Ltd.
 *
 * This file is part of MIRACL Core
 * (see https://github.com/miracl/core).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//
//  sha3.swift - Implementation of SHA3
//
//  Created by Michael Scott on 7/08/2017.
//

import Foundation

public struct SHA3{

    private var length:UInt64
	private var rate:Int
	private var len:Int
	private var s=[[UInt64]](repeating:[UInt64](repeating:0,count:5),count:5)

    	static let RC:[UInt64]=[
		0x0000000000000001,0x0000000000008082,0x800000000000808A,0x8000000080008000,
		0x000000000000808B,0x0000000080000001,0x8000000080008081,0x8000000000008009,
		0x000000000000008A,0x0000000000000088,0x0000000080008009,0x000000008000000A,
		0x000000008000808B,0x800000000000008B,0x8000000000008089,0x8000000000008003,
		0x8000000000008002,0x8000000000000080,0x000000000000800A,0x800000008000000A,
		0x8000000080008081,0x8000000000008080,0x0000000080000001,0x8000000080008008]

	static public let HASH224:Int=28
	static public let HASH256:Int=32
	static public let HASH384:Int=48
	static public let HASH512:Int=64
	static public let SHAKE128:Int=16
	static public let SHAKE256:Int=32

	static let ROUNDS:Int=24

    	private static func rotl(_ x: UInt64,_ n: UInt32) -> UInt64
    	{
        	return ((x<<UInt64(n))|(x>>(64-UInt64(n))))
    	}

    	private mutating func transform()
    	{ /* basic transformation step */
        	var c=[UInt64](repeating:0,count:5)
		var d=[UInt64](repeating:0,count:5)
		var b=[[UInt64]](repeating:[UInt64](repeating:0,count:5),count:5)

		for k in 0 ..< SHA3.ROUNDS
		{
			c[0]=s[0][0]^s[0][1]^s[0][2]^s[0][3]^s[0][4]
			c[1]=s[1][0]^s[1][1]^s[1][2]^s[1][3]^s[1][4]
			c[2]=s[2][0]^s[2][1]^s[2][2]^s[2][3]^s[2][4]
			c[3]=s[3][0]^s[3][1]^s[3][2]^s[3][3]^s[3][4]
			c[4]=s[4][0]^s[4][1]^s[4][2]^s[4][3]^s[4][4]

			d[0]=c[4]^SHA3.rotl(c[1],1)
			d[1]=c[0]^SHA3.rotl(c[2],1)
			d[2]=c[1]^SHA3.rotl(c[3],1)
			d[3]=c[2]^SHA3.rotl(c[4],1)
			d[4]=c[3]^SHA3.rotl(c[0],1)

			for i in 0 ..< 5 {
				for j in 0 ..< 5 {
					s[i][j]^=d[i]
				}
			}

			b[0][0]=s[0][0]
			b[1][3]=SHA3.rotl(s[0][1],36)
			b[2][1]=SHA3.rotl(s[0][2],3)
			b[3][4]=SHA3.rotl(s[0][3],41)
			b[4][2]=SHA3.rotl(s[0][4],18)

			b[0][2]=SHA3.rotl(s[1][0],1)
			b[1][0]=SHA3.rotl(s[1][1],44)
			b[2][3]=SHA3.rotl(s[1][2],10)
			b[3][1]=SHA3.rotl(s[1][3],45)
			b[4][4]=SHA3.rotl(s[1][4],2)

			b[0][4]=SHA3.rotl(s[2][0],62)
			b[1][2]=SHA3.rotl(s[2][1],6)
			b[2][0]=SHA3.rotl(s[2][2],43)
			b[3][3]=SHA3.rotl(s[2][3],15)
			b[4][1]=SHA3.rotl(s[2][4],61)

			b[0][1]=SHA3.rotl(s[3][0],28)
			b[1][4]=SHA3.rotl(s[3][1],55)
			b[2][2]=SHA3.rotl(s[3][2],25)
			b[3][0]=SHA3.rotl(s[3][3],21)
			b[4][3]=SHA3.rotl(s[3][4],56)

			b[0][3]=SHA3.rotl(s[4][0],27)
			b[1][1]=SHA3.rotl(s[4][1],20)
			b[2][4]=SHA3.rotl(s[4][2],39)
			b[3][2]=SHA3.rotl(s[4][3],8)
			b[4][0]=SHA3.rotl(s[4][4],14)

			for i in 0 ..< 5 {
				for j in 0 ..< 5 {
					s[i][j]=b[i][j]^(~b[(i+1)%5][j]&b[(i+2)%5][j])
				}
			}
			s[0][0]^=SHA3.RC[k]
		}
    	}

    /* Re-Initialise Hash function */
    	mutating func init_it(_ olen: Int)
    	{ /* initialise */
		    len=olen
		    rate=200-2*olen
    	}

    	public init(_ olen: Int)
    	{
      		for i in 0 ..< 5 {
			    for j in 0..<5 {
				    s[i][j]=0
			    }
		    }
        	length=0
		    len=0
		    rate=0
        	init_it(olen)
    	}

        public init(_ hc: SHA3)
        {
      		for i in 0 ..< 5 {
			    for j in 0..<5 {
				    s[i][j]=hc.s[i][j]
			    }
		    }
            length=hc.length
            len=hc.len
            rate=hc.rate
        }

    /* process a single byte */
    public mutating func process(_ byt: UInt8)
    { /* process the next message byte */
		let cnt=Int(length%UInt64(rate))
		let b=cnt%8
		let ind=cnt/8
		let i=ind%5
		let j=ind/5
		s[i][j]^=(UInt64(byt)&0xff)<<UInt64(8*b)
		length+=1
		if cnt+1 == rate {transform()}
    }

    /* process an array of bytes */
    public mutating func process_array(_ b: [UInt8])
    {
        for i in 0 ..< b.count {process((b[i]))}
    }

    /* process a 32-bit integer */
    public mutating func process_num(_ n:Int32)
    {
        process(UInt8((n>>24)&0xff))
        process(UInt8((n>>16)&0xff))
        process(UInt8((n>>8)&0xff))
        process(UInt8(n&0xff))
    }


	public mutating func squeeze(_ buff:inout [UInt8],_ olen: Int)
	{
		var done=false
		var m: Int = 0
	/* extract by columns */
		while true {
			for j in 0 ..< 5 {
				for i in 0 ..< 5
				{
					var el=s[i][j]
					for _ in 0 ..< 8
					{
						buff[m]=UInt8(el&0xff)
						m+=1
						if m>=olen || (m%rate)==0 {
							done=true
							break
						}
						el>>=8
					}
					if done {break}
				}
				if done {break}
			}
			if m>=olen {break}
			done=false
			transform()
		}
	}

    /* Generate Hash */
    public mutating func hash() -> [UInt8]
    { /* pad message and finish - supply digest */
        var digest=[UInt8](repeating: 0,count: len)
    	let q=rate-Int(length%UInt64(rate))
        if q==1 {process(0x86)}
        else {
            process(0x06)
            while Int(length%UInt64(rate)) != rate-1 {process(0x00)}
            process(0x80)
        }
        squeeze(&digest,len)
        return digest
	}

    public mutating func continuing_hash() -> [UInt8]
    {
        var sh=SHA3(self)
        return sh.hash()
    }    

    public mutating func shake(_ digest:inout [UInt8],_ olen: Int)
	{
		let q=rate-Int(length%UInt64(rate))
		if q==1 {process(0x9f)}
		else {
			process(0x1f)
			while Int(length%UInt64(rate)) != rate-1 {process(0x00)}
			process(0x80)
		}
 		squeeze(&digest,olen)
	}

    public mutating func continuing_shake(_ digest:inout [UInt8],_ olen: Int)
    {
        var sh=SHA3(self)
        sh.shake(&digest,olen)
    }   
}

//916f6061fe879741ca6469b43971dfdb28b1a32dc36cb3254e812be27aad1d18
//afebb2ef542e6579c50cad06d2e578f9f8dd6881d7dc824d26360feebf18a4fa73e3261122948efcfd492e74e82e2189ed0fb440d187f382270cb455f21dd185
//98be04516c04cc73593fef3ed0352ea9f6443942d6950e29a372a681c3deaf4535423709b02843948684e029010badcc0acd8303fc85fdad3eabf4f78cae165635f57afd28810fc2

/*
public func TestSHA3()
{
    	let test=String("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu")

    	var digest=[UInt8](repeating: 0,count: 172)

    	let t=[UInt8]( (test!).utf8)

	var sh=SHA3(SHA3.HASH256)
	for i in 0..<t.count {
		sh.process(t[i])
	}
	sh.hash(&digest)

       	for i in 0 ..< 32
        {
            let h=String(digest[i],radix:16)
            print("\(h)", terminator: "")
        }
        print("")

	sh=SHA3(SHA3.HASH512)
	for i in 0..<t.count {
		sh.process(t[i])
	}
	sh.hash(&digest)

       	for i in 0 ..< 64
        {
            let h=String(digest[i],radix:16)
            print("\(h)", terminator: "")
        }
        print("")

	sh=SHA3(SHA3.SHAKE256)
	for i in 0..<t.count {
		sh.process(t[i])
	}
	sh.shake(&digest,72)

       	for i in 0 ..< 72
        {
            let h=String(digest[i],radix:16)
            print("\(h)", terminator: "")
        }
        print("")
}

TestSHA3()  */
